11.OpenGL ES滤镜
给图像添加滤镜本质就是图片处理,也就是对图片的像素进行计算,简单来说,图像处理的方法可以分为三类:
- 点算:当前像素的处理只和自身的像素值有关,和其他像素无关,比如灰度处理。
- 领域算:当前像素的处理需要和相邻的一定范围内的像素有关,比如高斯模糊。
- 全局算:在全局上对所有像素进行统一变换,比如几何变换。
灰色滤镜
和前一篇文章基本一样,只是修改一下片段着色器的代码,原理就是在片段着色器中去处理颜色,让RGB三个通道的颜色取均值:
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
in vec2 v_texCoord;
out vec4 outColor;
uniform samplerExternalOES s_texture;
//灰度滤镜,具体去处理颜色
void grey(inout vec4 color){
float weightMean = color.r * 0.3 + color.g * 0.59 + color.b * 0.11;
color.r = color.g = color.b = weightMean;
}
void main(){
//拿到颜色值
vec4 tmpColor = texture(s_texture, v_texCoord);
//对颜色值进行处理
grey(tmpColor);
//将处理后的颜色值输出到颜色缓冲区
outColor = tmpColor;
}
反色滤镜
RGB三个通道的颜色取反,而alpha通道不变。
灰色滤镜
让RGB三个通道的颜色取均值
位移滤镜
纹理默认传入的读取范围是(0,0)到(1,1)内的颜色值。如果对读取的位置进行调整修改,那么就可以做出各种各样的效果,例如缩放动画就是让读取的范围改成(-1,-1)到(2,2)。
看完了疯了是不是,要做个滤镜效果,各种计算我实在弄不明白
最简单的方法就是通过LUT方法,通过设计师提供的LUT文件来实现预定的滤镜效果。基本思路如下:
- 准备LUT文件
- 加载LUT文件到OpenGL纹理
- 将纹理传递给片段着色器
- 根据LUT,在片段着色器中对图像的颜色值进行映射,得到滤镜后的颜色进行输出
离屏渲染
之前已经将相机的预览数据输出到OpenGL的纹理上,渲染的时候OpenGL直接将纹理渲染到屏幕上。但是如果想要对纹理进行进一步的处理,就不能直接渲染到屏幕上,而是需要先渲染到屏幕外的缓冲区(FrameBuffer)处理完后再渲染到屏幕。渲染到缓冲区的操作就是离屏渲染。主要步骤如下:
- 准备离屏渲染所需要的FrameBuffer和纹理对象。
- 切换渲染目标(屏幕->缓冲区)
- 执行渲染
- 重置渲染目标(缓冲区 -> 屏幕)
视频播放滤镜实现
首先需要在GLSurfaceView.Renderer的实现类中提供一个setFilter()的方法,然后在onDrawFrame()的时候再去调用这个Filter的onDraw()方法,所以滤镜的编写主要是靠基类。
- 先抽取BaseFilter类,封装好每个滤镜的着色器及onDraw等方法。
- 不同的滤镜都继承该BaseFilter类,然后实现有区别的部分。其实每个不同滤镜的区别就是着色器的不同。
- 在Renderder接口的实现类中去创建不同的Filter类,然后在onDrawFrame方法中去调用该filter.onDraw方法。
滤镜的不同大部分只需要修改片元着色器就可以了。
滤镜的基类
- onCreate():创建
- onSizeChange():滤镜尺寸改变
- onDraw():绘制每一帧
- onDestroy():销毁,用于资源回收。
public class BaseFilter {
private static final int POSITION_COMPONENT_COUNT = 2;
private static final int TEXTURE_COMPONENT_COUNT = 2;
protected int mProgram;
@RawRes
private int mVertexShaderResId;
@RawRes
private int mFragmentShaderResId;
private boolean mInited;
private int vertexPosition;
private int texturePosition;
private int samplerTexturePosition;
//渲染线程
private LinkedList<Runnable> mRunOnDraw = new LinkedList();
public BaseFilter() {
this(R.raw.video_no_filter_vertex_shader, R.raw.video_no_filter_fragment_shader);
}
public BaseFilter(@RawRes int vertexShaderResId, @RawRes int fragmentShaderResId) {
mVertexShaderResId = vertexShaderResId;
mFragmentShaderResId = fragmentShaderResId;
}
public void init() {
if (!mInited) {
onInit();
mInited = true;
onInited();
}
}
public void onInit() {
handleProgram(MyApplication.getInstance(), mVertexShaderResId, mFragmentShaderResId);
vertexPosition = glGetAttribLocation("vPosition");
texturePosition = glGetAttribLocation("vCoordPosition");
samplerTexturePosition = glGetUniformLocation("uSamplerTexture");
}
/**
* readResource -> compileShader -> linkProgram -> useProgram
*
* @param context
* @param vertexShader
* @param fragmentShader
*/
protected void handleProgram(@NonNull Context context, @RawRes int vertexShader, @RawRes int fragmentShader) {
String vertexShaderStr = ResReadUtils.readResource(context, vertexShader);
int vertexShaderId = ShaderUtils.compileVertexShader(vertexShaderStr);
//编译片段着色程序
String fragmentShaderStr = ResReadUtils.readResource(context, fragmentShader);
int fragmentShaderId = ShaderUtils.compileFragmentShader(fragmentShaderStr);
//连接程序
mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
//在OpenGLES环境中使用程序
GLES30.glUseProgram(mProgram);
}
public void onInited() {
}
public final void destroy() {
mInited = false;
GLES30.glDeleteProgram(mProgram);
onDestroy();
}
public void onDestroy() {
}
public void onDraw(final int textureId, final FloatBuffer mVertextBuffer,
final FloatBuffer mTextureBuffer) {
runPendingOnDrawTasks();
if (!mInited) {
return;
}
glVertexAttribPointer(vertexPosition, POSITION_COMPONENT_COUNT, GLES30.GL_FLOAT, false, 0, mVertextBuffer);
glVertexAttribPointer(texturePosition, TEXTURE_COMPONENT_COUNT, GLES30.GL_FLOAT, false, 0, mTextureBuffer);
GLES30.glEnableVertexAttribArray(vertexPosition);
GLES30.glEnableVertexAttribArray(texturePosition);
GLES30.glUniform1i(samplerTexturePosition, 0);
// if (textureId != GL.NO_TEXTURE) {
// GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
// GLES20.glUniform1i(glUniformTexture, 0);
// }
onDrawArraysPre();
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
GLES30.glDisableVertexAttribArray(vertexPosition);
GLES30.glDisableVertexAttribArray(texturePosition);
}
protected void runPendingOnDrawTasks() {
while (!mRunOnDraw.isEmpty()) {
mRunOnDraw.removeFirst().run();
}
}
/**
* 设置着色器中对象float值
*/
protected void setFloat(final int location, final float floatValue) {
runOnDraw(new Runnable() {
@Override
public void run() {
GLES30.glUniform1f(location, floatValue);
}
});
}
/**
* 设置着色器中对象组值float值
*/
protected void setFloatVec2(final int location, final float[] arrayValue) {
runOnDraw(new Runnable() {
@Override
public void run() {
GLES30.glUniform2fv(location, 1, FloatBuffer.wrap(arrayValue));
}
});
}
/**
* 设置着色中数组值
*/
protected void setFloatVec3(final int location, final float[] arrayValue) {
runOnDraw(new Runnable() {
@Override
public void run() {
GLES20.glUniform3fv(location, 1, FloatBuffer.wrap(arrayValue));
}
});
}
/**
* 设置着色器中对象组值float值
*/
protected void setFloatVec4(final int location, final float[] arrayValue) {
runOnDraw(new Runnable() {
@Override
public void run() {
GLES20.glUniform4fv(location, 1, FloatBuffer.wrap(arrayValue));
}
});
}
/**
* 设置着色器中3维矩阵的值
*/
protected void setUniformMatrix3f(final int location, final float[] matrix) {
runOnDraw(new Runnable() {
@Override
public void run() {
GLES20.glUniformMatrix3fv(location, 1, false, matrix, 0);
}
});
}
/**
* 设置着色器中4维矩阵的值
*/
protected void setUniformMatrix4f(final int location, final float[] matrix) {
runOnDraw(new Runnable() {
@Override
public void run() {
GLES20.glUniformMatrix4fv(location, 1, false, matrix, 0);
}
});
}
protected void runOnDraw(Runnable runnable) {
synchronized (mRunOnDraw) {
mRunOnDraw.addLast(runnable);
}
}
protected void onDrawArraysPre() {
}
protected void glViewport(int x, int y, int width, int height) {
GLES30.glViewport(x, y, width, height);
}
protected void glClearColor(float red, float green, float blue, float alpha) {
GLES30.glClearColor(red, green, blue, alpha);
}
protected void glClear(int mask) {
GLES30.glClear(mask);
}
protected static void glEnableVertexAttribArray(int index) {
GLES30.glEnableVertexAttribArray(index);
}
protected void glDisableVertexAttribArray(int index) {
GLES30.glDisableVertexAttribArray(index);
}
protected int glGetAttribLocation(String name) {
return GLES30.glGetAttribLocation(mProgram, name);
}
protected int glGetUniformLocation(String name) {
return GLES30.glGetUniformLocation(mProgram, name);
}
protected void glUniformMatrix4fv(int location, int count, boolean transpose, float[] value, int offset) {
GLES30.glUniformMatrix4fv(location, count, transpose, value, offset);
}
protected void glDrawArrays(int mode, int first, int count) {
GLES30.glDrawArrays(mode, first, count);
}
protected void glDrawElements(int mode, int count, int type, java.nio.Buffer indices) {
GLES30.glDrawElements(mode, count, type, indices);
}
protected void orthoM(String name, int width, int height) {
ProjectionMatrixUtil.orthoM(mProgram, width, height, name);
}
protected void glVertexAttribPointer(
int indx,
int size,
int type,
boolean normalized,
int stride,
java.nio.Buffer ptr) {
GLES30.glVertexAttribPointer(indx, size, type, normalized, stride, ptr);
}
protected void glActiveTexture(int texture) {
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
}
protected void glBindTexture(int target, int texture) {
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texture);
}
protected void glUniform1i(int location, int x) {
GLES20.glUniform1i(location, x);
}
public int getProgram() {
return mProgram;
}
}
在Render中改变的地方就是onDrawFrame()方法中去调用Filter.onDraw()方法:
@Override
public void onDrawFrame(GL10 gl) {
glClear(GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
adjustVideoSize();
synchronized (this) {
if (mUpdateSurfaceTexture) {
mSurfaceTexture.updateTexImage();
mUpdateSurfaceTexture = false;
}
}
runAll(mRunOnDraw);
mFilter.onDraw(mTextureId, mVertextBuffer, mTextureBuffer);
}
常用概念整理
- OpenGL: 一个定义了函数布局和输出的图形API的正式规范。
- GLAD: 一个拓展加载库,用来为我们加载并设定所有OpenGL函数指针,从而让我们能够使用所有(现代)OpenGL函数。
- 视口(Viewport): 我们需要渲染的窗口。
- 图形管线(Graphics Pipeline): 一个顶点在呈现为像素之前经过的全部过程。
- 着色器(Shader): 一个运行在显卡上的小型程序。很多阶段的图形管道都可以使用自定义的着色器来代替原有的功能。
- 标准化设备坐标(Normalized Device Coordinates, NDC): 顶点在通过在剪裁坐标系中剪裁与透视除法后最终呈现在的坐标系。所有位置在NDC下-1.0到1.0的顶点将不会被丢弃并且可见。
- 顶点缓冲对象(Vertex Buffer Object): 一个调用显存并存储所有顶点数据供显卡使用的缓冲对象。
- 顶点数组对象(Vertex Array Object): 存储缓冲区和顶点属性状态。
- 元素缓冲对象(Element Buffer Object,EBO),也叫索引缓冲对象(Index Buffer Object,IBO): 一个存储元素索引供索引化绘制使用的缓冲对象。
- Uniform: 一个特殊类型的GLSL变量。它是全局的(在一个着色器程序中每一个着色器都能够访问uniform变量),并且只需要被设定一次。
- 纹理(Texture): 一种包裹着物体的特殊类型图像,给物体精细的视觉效果。
- 纹理缠绕(Texture Wrapping): 定义了一种当纹理顶点超出范围(0, 1)时指定OpenGL如何采样纹理的模式。
- 纹理过滤(Texture Filtering): 定义了一种当有多种纹素选择时指定OpenGL如何采样纹理的模式。这通常在纹理被放大情况下发生。
- 多级渐远纹理(Mipmaps): 被存储的材质的一些缩小版本,根据距观察者的距离会使用材质的合适大小。
- 纹理单元(Texture Units): 通过绑定纹理到不同纹理单元从而允许多个纹理在同一对象上渲染。
- 向量(Vector): 一个定义了在空间中方向和/或位置的数学实体。
- 矩阵(Matrix): 一个矩形阵列的数学表达式。
- GLM: 一个为OpenGL打造的数学库。
- 局部空间(Local Space): 一个物体的初始空间。所有的坐标都是相对于物体的原点的。
- 世界空间(World Space): 所有的坐标都相对于全局原点。
- 观察空间(View Space): 所有的坐标都是从摄像机的视角观察的。
- 裁剪空间(Clip Space): 所有的坐标都是从摄像机视角观察的,但是该空间应用了投影。这个空间应该是一个顶点坐标最终的空间,作为顶点着色器的输出。OpenGL负责处理剩下的事情(裁剪/透视除法)。
- 屏幕空间(Screen Space): 所有的坐标都由屏幕视角来观察。坐标的范围是从0到屏幕的宽/高。
- LookAt矩阵: 一种特殊类型的观察矩阵,它创建了一个坐标系,其中所有坐标都根据从一个位置正在观察目标的用户旋转或者平移。
- 欧拉角(Euler Angles): 被定义为偏航角(Yaw),俯仰角(Pitch),和滚转角(Roll)从而允许我们通过这三个值构造任何3D方向。
- 邮箱 :[email protected]
- Good Luck!